package org.zoomdy.gdbserver.extender;

import java.io.*;
import java.net.Socket;
import java.util.Arrays;

public abstract class End extends Node
{
    public boolean start()
    {
        this._byteBuffer = new byte[this.BUFFER_SIZE];
        this._byteBufferLength = 0;
        this._byteBufferOffset = 0;
        this._lastPkt = null;
        this._ackMode = true;
        
        if(this.getClass().getName().equals("org.zoomdy.gdbserver.extender.Head"))
        {
            this._prefixGetPacket = "->";
            this._prefixPutPacket = "<-";
        }
        else if(this.getClass().getName().equals("org.zoomdy.gdbserver.extender.Tail"))
        {
            this._prefixGetPacket = "<---";
            this._prefixPutPacket = "--->";
        }
        else
        {
            this._prefixGetPacket = "***ERROR***";
            this._prefixPutPacket = "***ERROR***";
        }
        
        return true;
    }
    
    private byte getByte() throws IOException
    {
        byte retval;
        
        if(_byteBufferLength == _byteBufferOffset)
        {
            _byteBufferLength = this._inStream.read(_byteBuffer);
            if(_byteBufferLength <= 0)
            {
                _clientSocket.close();
                _byteBufferLength = 0;
                _byteBufferOffset = 0;
                
                throw new IOException("eof");
            }
            _byteBufferOffset = 0;
        }
        
        retval = _byteBuffer[_byteBufferOffset];
        _byteBufferOffset++;
        
        return retval;
    }

    private Packet fetchPacket() throws IOException
    {
        byte[] packetBuffer = new byte[this.BUFFER_SIZE];
        int offset = 0;
        byte b;
        
        // '$' already read by getPacket()
        packetBuffer[0] = '$';
        offset++;
        
        // get packet-data
        do
        {
            b = this.getByte();
            packetBuffer[offset] = b;
            offset++;
            
            if(b == '#')
            {
                break;
            }
            
            if(b == '{')
            {
                packetBuffer[offset] = this.getByte();
                offset++;
            }
        }
        while(b != '#');
        
        // get checksum
        packetBuffer[offset] = this.getByte();
        offset++;                
        packetBuffer[offset] = this.getByte();
        offset++;
        
        Packet pkt = new Packet(Arrays.copyOf(packetBuffer, offset));
        
        return pkt;
    }
    
    private Packet getPacketInner()
    {
        byte[] raw;
        Packet pkt = null;
        byte b;

        try
        {
            b = this.getByte();
            switch(b)
            {
            case '$':
                pkt = this.fetchPacket();
                break;
                
            case '+':
            case '-':
            case Packet.CTRL_C:
                raw = new byte[1];
                raw[0] = b;
                pkt = new Packet(raw);
                break;
                
            default:
                break;
            }
            
            return pkt;
        }
        catch(IOException e)
        {
            return null;
        }
    }
    
    protected Packet getPacket()
    {
        Packet pkt;
        while(true)
        {
            pkt = this.getPacketInner();
            if(pkt == null)
            {
                break;
            }
            
            printPacket(this._prefixGetPacket, pkt);
            
            if(pkt.isNormal())
            {
                // 常规包，根据需要返回确认包，然后返回数据包
                if(this._ackMode)
                {
                    this.putPacket(Packet.makeAckPacket());
                }
                
                break;
            }
            else if(pkt.isCtrlC())
            {
                // Ctrl+C，返回数据包
                break;
            }
            else if(pkt.isNack())
            {
                // Nack，重发最后的数据包
                this.putPacket(this._lastPkt);
            }
            else
            {
                // 确认包，忽略
            }
        }
        
        return pkt;
    }
    
    protected boolean putPacket(Packet pkt)
    {
        // 将输入的响应通过Socket返回给GDB
        try
        {
            printPacket(this._prefixPutPacket, pkt);
            this._outStream.write(pkt.getRaw());
            this._lastPkt = pkt;
            return true;
        }
        catch(IOException e)
        {
            e.printStackTrace();
            return false;
        }
    }
    
    protected void setAckMode(boolean ack)
    {
        this._ackMode = ack;
    }
    
    private synchronized void printPacket(String prefix, Packet pkt)
    {
        if(this._printPacket)
        {
            if(pkt.startsWith("X"))
            {
                byte[] raw = pkt.getRaw();
                int i;
                for(i = 0; i < raw.length; i++)
                {
                    if(raw[i] == ':')
                    {
                        break;
                    }
                }
                System.out.print(prefix);
                System.out.print(new String(raw, 0, i + 1));
                System.out.println("***BinaryData***");
            }
            else
            {
                System.out.print(prefix);
                System.out.println(pkt.toRawString());
            }
        }
    }
    
    protected void setPrintPacket(boolean print)
    {
        this._printPacket = print;
    }
    
    protected boolean getPrintPacket()
    {
        return this._printPacket;
    }
    
    protected Socket _clientSocket;
    protected InputStream _inStream;
    protected OutputStream _outStream;
    private byte[] _byteBuffer;
    private int    _byteBufferLength;
    private int    _byteBufferOffset;
    private Packet _lastPkt;
    private boolean _ackMode;
    private String _prefixGetPacket;
    private String _prefixPutPacket;
    private boolean _printPacket;
    
    protected final int BUFFER_SIZE = 1024 * 1024;
}
